鼬~~哩賀,我是寫程式的山姆老弟,今天來一探究竟 ActiveSupport 這個非常常見的 gem,到底在 Rails 中扮演什麼樣的角色!
Active Support Core Extensions - Ruby on Rails Guides
為什麼這樣說呢,是因為 ActiveSupport 對於原生的 Ruby 做了很多擴充,就像是 Ruby 穿了神裝一樣,變得更強大、變得更多功能,也就是很多人對 Rails 說的 magic,我想 ActiveSupport 應該是蠻大的功臣,我們接下去看一下 ActiveSupport 做了哪些擴充吧~
相信各位 Rails 開發者的你各位,都有用過 .present?
這東東,用來判斷物件是不是存在的 method,在原生 Ruby 是沒有這個 method 的,原生的 Ruby 只有 .nil?
,來判斷 Object 是不是空的的判斷方式
.present?
就是 ActiveSupport 幫所有物件新增的一個 magic method 的其中之一,其他 magic methods 還有:
blank?
presence
duplicable?
deep_dup
try
class_eval(*args, &block)
acts_like?(duck)
to_param
to_query
with_options
以上是 ActiveSupport 針對 Object
這個 Class 所做的擴充,也就是對所有物件都適用,除了對 Object
擴充之外,還有對 Module、對 Class、對 String、對 Symbol、對 Numeric、對 Integer、對 BigDecimal、對 Enumerable、對 Array、對 Hash、對 Regexp、對 Range、對 Date、對 DateTime、對 Time、對 File、對 NameError、對 LoadError、對 Pathname,沒錯,對超多 Class 擴充,總之就是很大一包!
ActiveSupport 的內容很多XD,這一篇先將 ActiveSupport 擴充 Ruby 的部分列出,再加上官方有提供範例、同時覺得有助於理解的,就會把範例一起寫下來
blank?
present?
presence
host = config[:host].presence || 'localhost'
duplicable?
deep_dup
try
class_eval
acts_like?
to_param
7.to_param # => "7"
"Tom & Jerry".to_param # => "Tom & Jerry"
[0, true, String].to_param # => "0/true/String"
to_query
[3.4, -45.6].to_query('sample') # => "sample%5B%5D=3.4&sample%5B%5D=-45.6"
{c: 3, b: 2, a: 1}.to_query # => "a=1&b=2&c=3"
{id: 89, name: "John Smith"}.to_query('user') # => "user%5Bid%5D=89&user%5Bname%5D=John+Smith"
with_options
instance_values
instance_variable_names
in?
alias_attribute
attr_internal_reader
, attr_internal_writer
, and attr_internal_accessor
mattr_reader
, mattr_writer
, and mattr_accessor
module_parent
module_parent_name
module_parents
anonymous?
delegate
delegate_missing_to
redefine_method
class_attribute
cattr_reader
, cattr_writer
, and cattr_accessor
subclasses
descendants
html_safe
, html_safe?
, raw
remove
squish
" \n foo\n\r \t bar \n".squish # => "foo bar"
truncate
, truncate_bytes
, truncate_words
"Oh dear! Oh dear! I shall be late!".truncate(20) # => "Oh dear! Oh dear!..."
"Oh dear! Oh dear! I shall be late!".truncate(20, omission: ' ...more') # "Oh dear! Oh ...more"
"Oh dear! Oh dear! I shall be late!".truncate(18) # => "Oh dear! Oh dea..."
"Oh dear! Oh dear! I shall be late!".truncate(18, separator: ' ') # => "Oh dear! Oh..."
inquiry
"production".inquiry.production? # => true
"active".inquiry.inactive? # => false
starts_with?
, ends_with?
strip_heredoc
if options[:usage]
puts <<-USAGE.strip_heredoc
This command does such and such.
Supported options are:
-h This message
...
USAGE
end
indent
puts <<EOS.indent(2)
def some_method
some_code
end
EOS
at
, from
, to
first
, last
pluralize
, singularize
"table".pluralize # => "tables"
"ruby".pluralize # => "rubies"
"equipment".pluralize # => "equipment"
"dude".pluralize(0) # => "dudes"
"dude".pluralize(1) # => "dude"
"dude".pluralize(2) # => "dudes"
"tables".singularize # => "table"
"rubies".singularize # => "ruby"
"equipment".singularize # => "equipment"
camelize
/ camelcase
, underscore
"admin_user".camelize # => "AdminUser"
"backoffice/session".camelize # => "Backoffice::Session"
"visual_effect".camelize(:lower) # => "visualEffect"
"AdminUser".underscore # => "admin_user"
"Backoffice::Session".underscore # => "backoffice/session"
"visualEffect".underscore # => "visual_effect"
titleize
/ titlecase
, dasherize
"alice in wonderland".titleize # => "Alice In Wonderland"
"fermat's enigma".titleize # => "Fermat's Enigma"
"name".dasherize # => "name"
"contact_data".dasherize # => "contact-data"
demodulize
, deconstantize
"Product".demodulize # => "Product"
"Backoffice::UsersController".demodulize # => "UsersController"
"Admin::Hotel::ReservationUtils".demodulize # => "ReservationUtils"
"::Inflections".demodulize # => "Inflections"
"".demodulize # => ""
"Product".deconstantize # => ""
"Backoffice::UsersController".deconstantize # => "Backoffice"
"Admin::Hotel::ReservationUtils".deconstantize # => "Admin::Hotel"
parameterize
"John Smith".parameterize # => "john-smith"
"John Smith".parameterize(preserve_case: true) # => "John-Smith"
"John Smith".parameterize(separator: "_") # => "john\_smith"
tableize
= pluralize + underscore, classify
"Person".tableize # => "people"
"Invoice".tableize # => "invoices"
"InvoiceLine".tableize # => "invoice_lines"
"people".classify # => "Person"
"invoices".classify # => "Invoice"
"invoice_lines".classify # => "InvoiceLine"
"highrise_production.companies".classify # => "Company"
constantize
"Integer".constantize # => Integer
"String".constantize # => String
humanize
"name".humanize # => "Name"
"author_id".humanize # => "Author"
"author_id".humanize(capitalize: false) # => "author"
"comments_count".humanize # => "Comments count"
"_id".humanize # => "Id"
foreign_key
"User".foreign_key # => "user_id"
"InvoiceLine".foreign_key # => "invoice_line_id"
"Admin::Session".foreign_key # => "session_id"
to_date
, to_time
, to_datetime
"2010-07-27".to_date # => Tue, 27 Jul 2010
"2010-07-27 23:37:00".to_time # => 2010-07-27 23:37:00 +0200
"2010-07-27 23:37:00".to_datetime # => Tue, 27 Jul 2010 23:37:00 +0000
還有發現一個酷東西(注意:這不是 ActiveSupport 的內容)
DateTime.parse("2010-07-27 23:42:00") # => Tue, 27 Jul 2010 23:42:00 +0000
DateTime._parse("2010-07-27 23:42:00") # => {:hour=>23, :min=>42, :sec=>0, :year=>2010, :mon=>7, :mday=>27}
Date.parse("2010-07-27 23:42:00") # => Tue, 27 Jul 2010
Date._parse("2010-07-27 23:42:00") # => {:hour=>23, :min=>42, :sec=>0, :year=>2010, :mon=>7, :mday=>27}
starts_with?
, ends_with?
bytes
, kilobytes
, megabytes
, gigabytes
, terabytes
, petabytes
, exabytes
2.kilobytes # => 2048
3.megabytes # => 3145728
3.5.gigabytes # => 3758096384
-4.exabytes # => -4611686018427387904
seconds
, minutes
, hours
, days
, weeks
, fortnights
# equivalent to Time.current.advance(days: 1)
1.day.from_now
# equivalent to Time.current.advance(weeks: 2)
2.weeks.from_now
# equivalent to Time.current.advance(days: 4, weeks: 5)
(4.days + 5.weeks).from_now
to_fs
(rails 7 才有哦,正確來說是 ActiveSupport 7.x 版本以上才有,6.x 版本以下叫做 to_s
)
100.to_fs(:percentage, precision: 0) # => 100%
1234567890.506.to_fs(:currency, precision: 3) # => $1,234,567,890.506 可惜只有美金
12345678.05.to_fs(:delimited) # => 12,345,678.05
111.2345.to_fs(:rounded, precision: 2) # => 111.23
1234567890.to_fs(:human_size) # => 1.15 GB
1234567890.to_fs(:human) # => "1.23 Billion"
multiple_of?
2.multiple_of?(1) # => true
1.multiple_of?(2) # => false
ordinal
, ordinalize
1.ordinal # => "st"
2.ordinal # => "nd"
53.ordinal # => "rd"
2009.ordinal # => "th"
1.ordinalize # => "1st"
2.ordinalize # => "2nd"
53.ordinalize # => "53rd"
2009.ordinalize # => "2009th"
to_s
sum
[1, 2, 3].sum # => 6
(1..100).sum # => 5050
[[1, 2], [2, 3], [3, 4]].sum # => [1, 2, 2, 3, 3, 4]
%w(foo bar baz).sum # => "foobarbaz"
{a: 1, b: 2, c: 3}.sum # => [:a, 1, :b, 2, :c, 3]
(1..5).sum { |n| n * 2 } # => 30
[2].sum(10) { |n| n**3 } # => 18
index_by
invoices.index_by(&:number)
# => {'2009-032' => <Invoice ...>, '2009-008' => <Invoice ...>, ...}
index_with
post = Post.new(title: "hey there", body: "what's up?")
%i( title body ).index_with { |attr_name| post.public_send(attr_name) } # => { title: "hey there", body: "what's up?" }
many?
<% if pages.many? %>
<%= pagination_links %>
<% end %>
@see_more = videos.many? {|video| video.category == params[:category]}
exclude?
including
, excluding
pluck
pick
to
, from
, including
, excluding
, second
, third
, fourth
, fifth
, second_to_last
, third_to_last
, 還有個神奇的 fourty_two
(是個科幻梗,來自 銀河便車指南,代表宇宙一切的答案)
其實 ActiveSupport 大部分的 magic method 的實作都不難理解
extract!
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
odd_numbers = numbers.extract! { |number| number.odd? } # => [1, 3, 5, 7, 9]
numbers # => [0, 2, 4, 6, 8]
extract_options!
to_sentence
to_fs
to_xml
Array.wrap
deep_dup
in_groups_of
, in_groups
[1, 2, 3].in_groups_of(2) # => [[1, 2], [3, nil]]
[1, 2, 3].in_groups_of(2, 0) # => [[1, 2], [3, 0]]
[1, 2, 3].in_groups_of(2, false) # => [[1, 2], [3]]
[1, 2, 3, 4, 5, 6, 7].in_groups(3) # => [["1", "2", "3"], ["4", "5", nil], ["6", "7", nil]]
[1, 2, 3, 4, 5, 6, 7].in_groups(3, "0") # => [["1", "2", "3"], ["4", "5", "0"], ["6", "7", "0"]]
[1, 2, 3, 4, 5, 6, 7].in_groups(3, false) # => [["1", "2", "3"], ["4", "5"], ["6", "7"]]
<% sample.in_groups_of(3) do |a, b, c| %>
<tr>
<td><%= a %></td>
<td><%= b %></td>
<td><%= c %></td>
</tr>
<% end %>
split
[0, 1, -5, 1, 1, "foo", "bar"].split(1) # => [[0], [-5], [], ["foo", "bar"]]
(-5..5).to_a.split { |i| i.multiple_of?(4) } # => [[-5], [-3, -2, -1], [1, 2, 3], [5]]
to_xml
reverse_merge
, reverse_merge!
options = {a:1, b:2, c:3}
options.merge(a: 5) # => {:a=>5, :b=>2, :c=>3}
options.reverse_merge(a: 5) # => {:a=>1, :b=>2, :c=>3}
reverse_update
= reverse_merge!
deep_merge
, deep_merge!
{a: {b: 1}}.deep_merge(a: {c: 2}) # => {:a=>{:b=>1, :c=>2}}
deep_dup
except
, except!
stringify_keys
, stringify_keys!
, deep_stringify_keys
, deep_stringify_keys!
symbolize_keys
, symbolize_keys!
, deep_symbolize_keys
, deep_symbolize_keys!
to_options
= symbolize_keys
, to_options!
asset_valid_keys
deep_transform_values
, deep_transform_values!
hash = { person: { name: 'Rob', age: '28' } }
hash.deep_transform_values{ |value| value.to_s.upcase } # => {person: {name: "ROB", age: "28"}}
slice!
, extract!
hash = {a: 1, b: 2, c: 3}
rest = hash.slice!(:a) # => {:b=>2, :c=>3}
hash # => {:a=>1}
hash = {a: 1, b: 2, c: 3}
rest = hash.extract!(:a) # => {:a=>1}
hash # => {:b=>2, :c=>3}
with_indifferent_access
{a: 1}.with_indifferent_access["a"] # => 1
multiline?
to_s
(Date.today..Date.tomorrow).to_s # => "2009-10-25..2009-10-26"
(Date.today..Date.tomorrow).to_s(:db) # => "BETWEEN '2009-10-25' AND '2009-10-26'"
===
, include?
(1..10) === (3..7) # => true
(1...9) === (3..9) # => false
(1..10).include?(3..7) # => true
(1...9).include?(3..9) # => false
overlaps?
(1..10).overlaps?(7..11) # => true
(1...10).overlaps?(10..11) # => false
Date.current
(有時區; Time.now
是沒時區的)
beginning_of_week
/at_beginning_of_week
, end_of_week
/at_end_of_week
d = Date.new(2010, 5, 8) # => Sat, 08 May 2010
d.beginning_of_week # => Mon, 03 May 2010
d.beginning_of_week(:sunday) # => Sun, 02 May 2010
d.end_of_week # => Sun, 09 May 2010
d.end_of_week(:sunday) # => Sat, 08 May 2010
monday
, sunday
prev_week
/last_week
, next_week
d = Date.new(2010, 5, 9) # => Sun, 09 May 2010
d.next_week # => Mon, 10 May 2010
d.next_week(:saturday) # => Sat, 15 May 2010
d.prev_week # => Mon, 26 Apr 2010
d.prev_week(:saturday) # => Sat, 01 May 2010
d.prev_week(:friday) # => Fri, 30 Apr 2010
begining_of_month
/at_beginning_of_month
, end_of_month
/ at_end_of_month
, beginning_of_quarter
/at_beginning_of_quarter
, end_of_quarter
/at_end_of_quarter
, beginning_of_year
/at_beginning_of_year
, end_of_year
/ at_end_of_year
, beginning_of_day
/ at_beginning_of_day
, end_of_day
/ at_end_of_day
, midnight
/at_mid_night
, beginning_of_hour
/ at_beginning_of_hour
, end_of_hour
/ at_end_of_hour
, beginning_of_minute
/ at_beginning_of_minute
, end_of_minute
/ at_end_of_minute
years_ago
, years_since
, months_ago
, months_since
, weeks_ago
advance
, change
date = Date.new(2010, 6, 6)
date.advance(years: 1, weeks: 2) # => Mon, 20 Jun 2011
date.advance(months: 2, days: -2) # => Wed, 04 Aug 2010
Date.new(2010, 12, 23).change(year: 2011, month: 11) # => Wed, 23 Nov 2011
Date.new(2010, 1, 31).change(month: 2) # => ArgumentError: invalid date
ago
, since
date = Date.current # => Fri, 11 Jun 2010
date.ago(1) # => Thu, 10 Jun 2010 23:59:59 EDT -04:00
date = Date.current # => Fri, 11 Jun 2010
date.since(1) # => Fri, 11 Jun 2010 00:00:01 EDT -04:00
DateTime.current
seconds_since_midnight
now = DateTime.current # => Mon, 07 Jun 2010 20:26:36 +0000
now.seconds_since_midnight # => 73596
utc
/get_utc
, utc?
now = DateTime.current # => Mon, 07 Jun 2010 19:27:52 -0400
now.utc # => Mon, 07 Jun 2010 23:27:52 +0000
advance
, change
d = DateTime.current # => Thu, 05 Aug 2010 11:33:31 +0000
d.advance(years: 1, months: 1, days: 1, hours: 1, minutes: 1, seconds: 1) # => Tue, 06 Sep 2011 12:34:32 +0000
Time.current
advance
, change
all_day
, all_week
, all_month
, all_quarter
, all_year
prev_day
, next_day
, prev_month
, next_month
, prev_quarter
, next_quarter
, prev_year
, next_year
atomic_write
File.atomic_write(joined_asset_path) do |cache|
cache.write(join_asset_file_contents(asset_paths))
end
missing_name?
, is_missing?
def default_helper_module!
module_name = name.delete_suffix("Controller")
module_path = module_name.underscore
helper module_path
rescue LoadError => e
raise e unless e.is_missing? "helpers/#{module_path}_helper"
rescue NameError => e
raise e unless e.missing_name? "#{module_name}Helper"
end
existence
(Rails7 才有)
content = Pathname.new("file").existence&.read
ActiveSupport 真的超多東西,看完我也嚇一跳,有些 magic method 我看了一下 source code 之後,覺得確實有包成 magic method 會是比較好一點,就不需要自己手刻這些很方便、卻又很簡單的功能,明天會跟大家來一探 ActiveSupport 某些我覺得比較有印象的 source code,我們明天見~